/*
 * Copyright (C) 2013 rdrrlabs gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package serv

import (
    "appengine"
    "appengine/datastore"
    // "appengine/user" // FIXME remove if unused
    "errors"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "time"
)


//------------------------------

// GCM client defines the default lifespan of a registered id as 7 days.
const kServerLifespanHours = 7 * 24

// Structure stored in AppEngine's datastore
type GCMInfo struct {
    RegID   string      `crud:"key"`
    Created time.Time   `datastore:",noindex"`
    Expires time.Time   `datastore:",noindex"`
}

func (u GCMInfo) ToJsonTable() []string {
    // Converts a DataStore entity (the one matching DsStruct) into an array of strings
    // matching the fields expected in the CRUD html data table.
    return []string { u.RegID,
                      u.Created.Format("2006-01-02 15:04:05 MST"),
                      u.Expires.Format("2006-01-02 15:04:05 MST") }
}

//------------------------------

func RegisterGCMCrud(handlers Handlers) Handlers {

    ci := NewCrud("GCM", "/gcm", GCMInfo{}, &gcmCruder{})
    return ci.RegisterHandlers(handlers)
}

type gcmCruder struct {
    crudInfo *CrudInfo
}

// gcmCruder implements the Cruder interface

func (c *gcmCruder) RegisterHandlers(ci *CrudInfo, handlers Handlers) Handlers {
    c.crudInfo = ci
    desc := &HandlerDesc { Title:  "GCM",
                           URL:    "/timer",
                           Flags:  HandlerIsTab | HandlerIsSigned,
                           Groups: []string { GroupView, GroupAdmin } }
    handlers = append(handlers, &CrudHandler { desc, ci })

    desc     = &HandlerDesc { URL:   "/gcm/register",
                              Flags: HandlerIsPost }
    handlers = append(handlers, &CrudHandler { desc, ci })

    desc     = &HandlerDesc { URL:   "/gcm/unregister",
                              Flags: HandlerIsPost }
    handlers = append(handlers, &CrudHandler { desc, ci })
    return handlers
}


func (c *gcmCruder) CanCreateTab(p *CrudHandler, r *http.Request, ac appengine.Context, a *AccessInfo) bool {
    // Return true if it's OK to create a UI tab page for this request.
    // -- return p.Is(HandlerIsSigned) && a.Is(GroupView) -- FIXME is signed needed?
    return a.Is(GroupView)
}

func (c *gcmCruder) ValidateAdd(values url.Values) (entity Datastorer, err error) {
    return nil, errors.New("Invalid request")
}


// gcmCruder implements the CruderProcessRequester interface

func (p *gcmCruder) ProcessRequest(w http.ResponseWriter,
                                        r *http.Request,
                                        c appengine.Context,
                                        a *AccessInfo,
                                        urlBase string,
                                        urlRest string) (httpCode int, err error) {
    log.Printf("##DEBUG timer_gcm ProcessRequest %v %v", r.Method, urlBase)

    if r.Method == "POST" && urlBase == "/timer_gcm/register" {
        return p.processRegister(w, r, c, a)

    } else if r.Method == "POST" && urlBase == "/timer_gcm/unregister" {
            return p.processUnregister(w, r, c, a)
    }
    return http.StatusNotFound, nil
}

//------------------------------


func (p *gcmCruder) processRegister(w  http.ResponseWriter,
                                         r *http.Request,
                                         c  appengine.Context,
                                         a *AccessInfo) (httpCode int, err error) {
    blob, err := ioutil.ReadAll(r.Body)
    if err != nil {
        return http.StatusInternalServerError, err
    }
    v, err := url.ParseQuery(string(blob))
    regID := v.Get("regId")
    log.Printf("##DEBUG register regid: %v", regID)

    if regID != "" {
        err = datastore.RunInTransaction(c, func(tc appengine.Context) error {
            kind := p.crudInfo.Kind
            tkey := datastore.NewKey(c, kind, regID, 0, nil)

            var info GCMInfo
            terr := datastore.Get(tc, tkey, &info)

            now := time.Now()
            if terr == datastore.ErrNoSuchEntity {
                info.Created = now
            }
            info.RegID = regID
            info.Expires = now.Add(time.Duration(kServerLifespanHours) * time.Hour)

            _, terr  = datastore.Put(c, tkey, &info)
            if terr != nil {
                log.Printf("##DEBUG register Put.err: %v", terr.Error())
            }
            return terr
        }, nil ) //-- &datastore.TransactionOptions { XG: true } )

        if err == nil {
            return http.StatusOK, nil
        }
    }

    return http.StatusBadRequest, nil
}


func (p *gcmCruder) processUnregister(w  http.ResponseWriter,
                                            r *http.Request,
                                            c  appengine.Context,
                                            a *AccessInfo) (httpCode int, err error) {
    blob, err := ioutil.ReadAll(r.Body)
    if err != nil {
        return http.StatusInternalServerError, err
    }
    v, err := url.ParseQuery(string(blob))
    regID := v.Get("regId")
    log.Printf("##DEBUG *un*register regid: %v", regID)

    if regID != "" {
        err = datastore.RunInTransaction(c, func(tc appengine.Context) error {
                kind := p.crudInfo.Kind
                tkey := datastore.NewKey(c, kind, regID, 0, nil)

                terr := datastore.Delete(tc, tkey)
                if terr != nil {
                    log.Printf("##DEBUG *un*register Del.err: %v", terr.Error())
                }
                return terr
            }, nil ) //-- &datastore.TransactionOptions { XG: true } )

        if err == nil {
            return http.StatusOK, nil
        }
    }

    return http.StatusBadRequest, nil
}
